package com.lemon.boot.common.filter;

import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import com.lemon.boot.common.consts.RedisKeyConst;
import com.lemon.boot.common.entity.LoginUserDetail;
import com.lemon.boot.common.handler.HttpServletRequestHandler;
import com.lemon.boot.common.properties.JwtConfigProperties;
import com.lemon.boot.common.service.ElasticsearchService;
import com.lemon.boot.common.service.RedisService;
import com.lemon.boot.common.thread.pool.ExecutorThreadPoolGroup;
import com.lemon.boot.common.utils.*;
import com.lemon.boot.po.LogPO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author 李猛
 * @datetime 2024/2/26 15:56
 * @description Token认证及续签
 */
@Slf4j
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
    public RedisService redisService;
    public ElasticsearchService elasticsearchService;
    public JwtConfigProperties jwtConfigProperties;

    public TokenAuthenticationFilter(AuthenticationManager authenticationManager,
                                     RedisService redisService,
                                     ElasticsearchService elasticsearchService,
                                     JwtConfigProperties jwtConfigProperties) {
        super(authenticationManager);
        this.redisService = redisService;
        this.elasticsearchService = elasticsearchService;
        this.jwtConfigProperties = jwtConfigProperties;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        //1.判断是否是放行的静态资源
        String uri = request.getRequestURI();
        if (isPassUri(uri)) {
            chain.doFilter(request, response);
            return;
        }

        //2.获取token
        String token = JwtUtil.getToken(request);
        log.info("Token认证及续签,uri:{},token:{}", uri, token);

        //3.token不存在
        if (!StringUtils.hasText(token)) {
            chain.doFilter(request, response);
            return;
        }

        //4.当前用户
        LoginUserDetail currentUser = JwtUtil.getCurrentUser(token);

        //5.判断token是否过期
        if (!verifyUser(response, currentUser)) {
            return;
        }

        //6.日志记录
        this.doLog(request, currentUser);

        //7.设置认证信息
        SecurityUtil.setAuthentication(request, currentUser);

        chain.doFilter(request, response);
    }

    /**
     * 判断是否是放行的静态资源
     *
     * @param uri
     * @return
     */
    private boolean isPassUri(String uri) {
        List<String> passUriList = jwtConfigProperties.getAllPassUriByList();
        //1.判断是否为空
        if(CollectionUtils.isEmpty(passUriList) || !StringUtils.hasText(uri)){
            return false;
        }

        //2.正则表达式匹配
        return passUriList
                .stream()
                .map(item -> String.format("^%s",item.replace("**", ".*")))
                .map(Pattern::compile)
                .anyMatch(pattern -> pattern.matcher(uri).matches());
    }

    /**
     * 执行添加日志
     *
     * @param request
     * @param currentUser
     */
    private void doLog(HttpServletRequest request, LoginUserDetail currentUser) {
        final LogPO logPO = getLog(request, currentUser);
        ExecutorThreadPoolGroup.logExecutorService.execute(() -> elasticsearchService.add(logPO));
    }

    /**
     * 验证token
     *
     * @param response
     * @param currentUser
     * @return
     */
    private boolean verifyUser(HttpServletResponse response, LoginUserDetail currentUser) {
        //1.判断用户名是否存在
        if (!StringUtils.hasText(currentUser.getUsername())) {
            ResponseUtil.fail(response, HttpStatus.FORBIDDEN.value(), "用户名错误，请重新登录！");
            return false;
        }

        //2.redis里的 key
        String key = String.format("%s%s", RedisKeyConst.REDIS_KEY_LOGIN_USER, currentUser.getUsername());
        boolean hasUsername = redisService.has(key);
        if (!hasUsername) {
            ResponseUtil.fail(response, HttpStatus.FORBIDDEN.value(), "token过期，请重新登录！");
            return false;
        }

        //2.token续签
        boolean expire = redisService.expire(key, jwtConfigProperties.getExpireTime(), TimeUnit.MINUTES);
        if (!expire) {
            log.error("token续签失败，用户名：{}", currentUser.getUsername());
            ResponseUtil.fail(response, HttpStatus.FORBIDDEN.value(), "token过期，请重新登录！");
            return false;
        }
        return true;
    }

    /**
     * 获取日志
     *
     * @param request
     * @param currentUser
     * @return
     */
    private static LogPO getLog(HttpServletRequest request, LoginUserDetail currentUser) {
        //1.请求的URI
        String uri = request.getRequestURI();
        //2.请求的IP
        String ip = RequestUtil.getIp(request);
        //3.请求方法
        String method = request.getMethod();
        //4.请求参数
        String param = null;
        if (HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method)) {
            param = JSONUtil.toJsonStr(request.getParameterMap());
        } else if (request.getContentType().startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
            param = String.format("文件上传:%s", RequestUtil.getFileInfo(request));
        } else {
            param = new String(new HttpServletRequestHandler(request).getBody(), StandardCharsets.UTF_8);
        }

        //5.日志
        return new LogPO()
                .setId(IdUtil.getSnowflakeNextId())
                .setIp(ip).setMethod(method)
                .setUri(uri)
                .setParam(param)
                .setTenantId(currentUser.getTenantId())
                .setUserId(currentUser.getUserId())
                .setUserName(currentUser.getName())
                .setCreateTime(LocalDateUtil.toEpochMilli());
    }
}
